#include "CharacterToolMainWindow.h"
#include "ui_CharacterSheet.h"
#include "ui_dnd.h"
#include "EditSheet.h"

#include <Dialogs/AboutDialog.h>
#include <Dialogs/PreferencesDialog.h>
#include <Dialogs/AddRace.h>
#include <Dialogs/AddPower.h>
#include <Dialogs/AddFeat.h>
#include <Dialogs/AddTrait.h>
#include <Dialogs/AddSkill.h>
#include <Dialogs/AddClass.h>
#include <Dialogs/LoadSave.h>
#include <DndData/CharacterData.h>
#include <DndData/CharacterRecord.h>
#include <Models/DndDataTreeModel.h>
#include <Xml/XmlException.h>
#include <Xml/XmlFileNotFound.h>
#include <Xml/XmlDirectory.h>

#include <QTimer>
#include <QSettings>
#include <QDir>
#include <QMessageBox>
#include <QStringList>
#include <QPrinter>
#include <QPrintDialog>
#include <QPainter>
#include <QScrollArea>
#include <QDebug>

/*!
 * \mainpage DndTool D&D 4 Character generator
 * \section intro_sec Introduction
 * DndTool is a cross-platform GUI tool to assist in generating, editing
 * and maintaining characters and non-player characters for the Dungeons
 * and Dragons 4th edition game published by
 * <a href="http://www.wizards.com/dnd">Wizards of the Coast</a>.
 *
 * See the file README.html for more details.
 * \section arch_sec Design
 * DndTool uses Xml data files to store all information about the D&D
 * characters, and also to store the data relating to the game.  This means
 * that this application does not contain much of the information found in
 * the D&D Players Handbook, or other source materials.
 *
 * The reason for this is partly to avoid breaching the copyright of those
 * materials.  See the file README.html for more details.
 *
 * The data and program separation is also to allow for expansion.  If the
 * races available were coded here, then it might be harder to enter new races
 * when later expansions arrive.
 *
 * The GUI is a QMainWindow which hosts a toolbox displaying all facets of
 * the character.  However new characters are intended to be generated using
 * the NewCharacterWizard() dialog, which starts with abilities generation,
 * and goes thru all stages of character setup.  The main GUI can then be
 * used for tweaking the character, editing for leveling up and so on.
 *
 * Future versions may include a special wizard for quickly creating NPCs.
 *
 * \section impl_sec Implementation
 *
 * The root of the data storage is configured by the preferences dialog
 * and is available by the Xml::getDataRootDir() function.
 *
 * The data files are stored under the root in a directory structure like this:
 * \li Characters
 * \li Class
 * \li Race
 * \li Equipment
 * \li Feat
 * \li Skill
 *
 * Each directory contains Xml files (with no terminating .xml extension).  The
 * file names reflect the title of the data, for example "Dragonborn" for the Xml
 * data file about the Dragonborn race, is contained in the Race directory.
 *
 * The Class, Race, Equipment and Feat directories can also contain sub-directories
 * for powers and traits related.  For example powers related to the Dragonborn race,
 * such as the "Dragon Breath" racial power, are stored in the "Dragonborn.Power"
 * sub-directory.  The file name is obtained by calling safeDataPathName() on the
 * title of the power.
 *
 * The Add* dialogs are called from toolbar buttons for each of the main data types
 * to fill in the missing D&D Race, Class and other data.  Buttons within the
 * NewCharacterWizard also launch the Add* gui's.  These dialogs take all the details
 * of a given data item, and if the OK, button is pressed also take care of
 * serializing the data item as Xml.  Once serialized it can be refreshed back
 * in the calling window - caching of the data should make this fast.
 */

CharacterToolMainWindow::CharacterToolMainWindow(QWidget * parent, Qt::WindowFlags flags)
    : QMainWindow(parent, flags)
    , mSheet(new Ui::CharacterSheet())
    , mUi(new Ui::Dnd())
    , mCharacterData(0)
{
    qDebug() << ">>> CharacterToolMainWindow()";

    mUi->setupUi(this);

    connect(mUi->actionClass, SIGNAL(triggered()),
            this, SLOT(showClassDialog()));
    connect(mUi->actionFeat, SIGNAL(triggered()),
            this, SLOT(showFeatDialog()));
    connect(mUi->actionPower, SIGNAL(triggered()),
            this, SLOT(showPowerDialog()));
    connect(mUi->actionTrait, SIGNAL(triggered()),
            this, SLOT(showTraitDialog()));
    connect(mUi->actionSkill, SIGNAL(triggered()),
            this, SLOT(showSkillDialog()));
    connect(mUi->actionPreferences, SIGNAL(triggered()),
            this, SLOT(showPreferencesDialog()));
    mSheet->setupUi(mUi->subWindowSheet);
    setAnimated(false);

    // calling these static accessors here forces construction of the dialogs
    // at the time of construction of the main-window, since the accessors are
    // otherwise not threadsafe
    connect(AddClass::dialog(this), SIGNAL(statusMessage(const QString &)),
        statusBar(), SLOT(showMessage(QString)));
    connect(AddFeat::dialog(this), SIGNAL(statusMessage(const QString &)),
        statusBar(), SLOT(showMessage(QString)));
    connect(AddPower::dialog(this), SIGNAL(statusMessage(const QString &)),
        statusBar(), SLOT(showMessage(QString)));
    connect(AddRaceDialog::dialog(this), SIGNAL(statusMessage(const QString &)),
        statusBar(), SLOT(showMessage(QString)));
    connect(AddSkill::dialog(this), SIGNAL(statusMessage(const QString &)),
        statusBar(), SLOT(showMessage(QString)));
    connect(AddTrait::dialog(this), SIGNAL(statusMessage(const QString &)),
        statusBar(), SLOT(showMessage(QString)));
    connect(PreferencesDialog::dialog(this), SIGNAL(statusMessage(const QString &)),
        statusBar(), SLOT(showMessage(QString)));

    QTimer::singleShot(0, this, SLOT(checkInitialCondition()));
    qDebug() << "<<< CharacterToolMainWindow()";
}

CharacterToolMainWindow::~CharacterToolMainWindow()
{
    delete mSheet;
    delete mUi;
    delete mCharacterData;
}

bool CharacterToolMainWindow::ensureDataDirectory()
{
    qDebug() << ">>> ensureDataDirectory()";
    bool dataDirNeedsToBeInitialized = false;
    bool dataDirNeedsSetToDefault = false;
    bool success = false;
    while (true)
    {
        try {
            if (dataDirNeedsSetToDefault)
            {
                // data directory was not defined - set it to a default value
                QDir homeDir(QDir::homePath());
                QString dataDir = homeDir.absoluteFilePath("DndToolData");
                XmlDirectory::setDataRootDir(QDir(dataDir));
            }
            else if (dataDirNeedsToBeInitialized)
            {
                // data directory was defined but did not exist
                QSettings settings;
                QString dataRoot = settings.value("paths/dataroot", QString()).toString();
                XmlDirectory::setDataRootDir(QDir(dataRoot));
            }
            else
            {
                // try to construct on stack, if constructor throws, theres problems
                XmlDirectory xmlDir;
                Q_UNUSED(xmlDir);
            }
            success = true;
            break;
        }
        catch (XmlFileNotFound e)
        {
            if (dataDirNeedsToBeInitialized) // tried to do and failed
                break;
            dataDirNeedsToBeInitialized = true;
        }
        catch (XmlException e)
        {
            if (dataDirNeedsSetToDefault) // tried to do and failed
                break;
            dataDirNeedsSetToDefault = true;
        }
    }
    qDebug() << "<<< ensureDataDirectory()" << success;
    return success;
}

void CharacterToolMainWindow::checkInitialCondition()
{
    qDebug() << ">>> checkInitialCondition()";
    if (!ensureDataDirectory())
    {
        QSettings settings;
        QString dataRoot = settings.value("paths/dataroot", QString()).toString();
        QMessageBox::critical(this, tr("Exiting with fatal problem"),
                              tr("Problem with data folder: %1").arg(dataRoot));
        qFatal("Could not access data folder: %s", dataRoot.toLocal8Bit().constData());
    }
    DndDataTreeModel &model = DndDataTreeModel::getInstance();
    model.initialize();
    mUi->treeView->setModel(&model);
    XmlDirectory xmlDir;
    QDir charDir = xmlDir.dataDir(XmlPath::CHARACTER_BASE);
    QStringList chars = charDir.entryList(QDir::Files);
    if (chars.size() == 0)
    {
        QMessageBox::information(this, tr("No Character files found"),
                                 tr("Select \"New\" from the \"File\" menu to get started"));
    }
    else
    {
        QSettings settings;
        QString lastCharacter = settings.value("state/lastCharacter", QString()).toString();
        qDebug() << "last character key" << lastCharacter;
        if (lastCharacter.isEmpty())
            lastCharacter = chars[0];
        qDebug() << "character 0" << lastCharacter;
        load(lastCharacter);
    }
    qDebug() << "<<< checkInitialCondition()";
}

void CharacterToolMainWindow::configureDataPath()
{
    showPreferencesDialog();
}

void CharacterToolMainWindow::on_actionAbout_triggered()
{
    AboutDialog::dialog(this)->exec();
}

void CharacterToolMainWindow::load(const QString &name)
{
    mCharacterData = new CharacterData(name);
    qDebug() << "CharacterToolMainWindow::load(" << name << ")";
    try {
        mCharacterData->loadFromXml();
    }
    catch (XmlException e)
    {
        QMessageBox::critical(this,
                tr("Bad character: %1").arg(mCharacterData->title()),
                e.message());
        return;
    }
    qDebug() << "Successfully loaded";
    CharacterRecord record;
    mCharacterData->fill(&record);
    record.apply(mUi->subWindowSheet);

    QSettings settings;
    settings.setValue("state/lastCharacter", name);
}

void CharacterToolMainWindow::on_actionNew_triggered()
{

}

void CharacterToolMainWindow::showClassDialog()
{
    AddClass *dlg = AddClass::dialog();
    dlg->show();
    dlg->raise();
    dlg->activateWindow();
    dlg->initialize();
}

void CharacterToolMainWindow::showFeatDialog()
{
    AddFeat *dlg = AddFeat::dialog();
    dlg->show();
    dlg->raise();
    dlg->activateWindow();
    dlg->initialize();
}

void CharacterToolMainWindow::showPowerDialog()
{
    AddPower *dlg = AddPower::dialog();
    dlg->show();
    dlg->raise();
    dlg->activateWindow();
    dlg->initialize();
}

void CharacterToolMainWindow::showRaceDialog()
{
    AddRaceDialog *dlg = AddRaceDialog::dialog();
    dlg->show();
    dlg->raise();
    dlg->activateWindow();
    dlg->initialize();
}

void CharacterToolMainWindow::showSkillDialog()
{
    AddSkill *dlg = AddSkill::dialog();
    dlg->show();
    dlg->raise();
    dlg->activateWindow();
    dlg->initialize();
}

void CharacterToolMainWindow::showTraitDialog()
{
    AddTrait *dlg = AddTrait::dialog();
    dlg->show();
    dlg->raise();
    dlg->activateWindow();
    dlg->initialize();
}

void CharacterToolMainWindow::showPreferencesDialog()
{
    PreferencesDialog *dlg = PreferencesDialog::dialog();
    dlg->show();
    dlg->raise();
    dlg->activateWindow();
}

void CharacterToolMainWindow::on_actionPrint_triggered()
{
     QPrinter printer;

     QPrintDialog *dialog = new QPrintDialog(&printer, this);
     dialog->setWindowTitle(tr("Print Character"));
     if (dialog->exec() != QDialog::Accepted)
         return;

     QPainter painter;
     painter.begin(&printer);
     // qreal scaleX = (qreal)physicalDpiX() / (qreal)printer.physicalDpiX();
     // qreal scaleY = (qreal)physicalDpiY() / (qreal)printer.physicalDpiY();
     painter.setWindow(mUi->subWindowSheet->rect());
     // painter.scale(scaleX, scaleY);
     mUi->subWindowSheet->render(&painter);
     painter.end();
}

void CharacterToolMainWindow::on_actionSheet_triggered()
{
    CharacterRecord record;
    mCharacterData->fill(&record);

    qDebug() << "creating edit sheet";
    EditSheet *dlg = new EditSheet(&record, this);
    qDebug() << "setting target";
    dlg->setTarget(mUi->subWindowSheet);
    qDebug() << "execing EditSheet";
    dlg->exec();
    qDebug() << "Exec done, deleting";
    delete dlg;
    qDebug() << "Dialog deleted, exiting";
}
